#ifndef FREELIST_H
#define FREELIST_H

#include "BlockLinkedList.h"
#include "BlockGroup.h"

//This class represents the freelist, a list of free blocks on the disk

//author: Sean

class FreeList: public BlockLinkedList{

 public:

  /**
   *contructs a RAM representation of freelist
   *param:
   *fileName : name of file for disk object
   *createFreeList should be true if a free list has not yet been created
   */
  FreeList(const char* fileName, bool createFreeList){

      pthread_mutex_init(&freelistlock, NULL);
      char* file = (char*)fileName;
      this->m_disk = new Disk(file, 100, Disk::DEFAULT_BLOCK_SIZE);

      if (createFreeList){
	Block newBlock(Disk::DEFAULT_BLOCK_SIZE);
          int i;
          for(i = 0; i<this->m_disk->numberOfBlocks() - 1;i++){
              newBlock.SetBlockNumber(i);
              newBlock.SetPointer(i+1,0);
              newBlock.Write(this->m_disk);
              this->m_numberOfBlocks++;
          }
          newBlock.SetBlockNumber(i);
          newBlock.SetPointer(-1,0);
          newBlock.Write(this->m_disk);
          this->m_numberOfBlocks++;
          this->m_endBlockNumber = (this->m_disk->numberOfBlocks() - 1);
//          cout<< "creating free list"<<endl;
      }
      delete this->m_seek;
      this->m_seek = new Block(0,this->m_disk);
      this->m_startBlockNumber = m_seek->GetPointer(0);
      this->m_numberOfBlocks = this->CountBlocks();
      while(m_seek->GetPointer(0) != -1)
      {
          this->GetNextBlock();
      }
      this->m_endBlockNumber = m_seek->GetBlockNumber();
    //  this->m_endBlockNumber = this->m_seek->GetBlockNumber();
//      cout<< "starting block: "<< this->m_startBlockNumber<<endl;
//      cout<< "ending block: "<< this->m_endBlockNumber<<endl;
//      cout<< "number of blocks: "<<this->m_numberOfBlocks<<endl;
  }

  Block * UnlinkBlock()
  {
     //go to the first block in list
        Rewind();
        if (this->GetCurrentBlock()->GetBlockNumber() == -1){
            return this->GetCurrentBlock();
        }
        //get the pointer to next block
        int next_pointer = this->GetCurrentBlock()->GetPointer(0);
        //make a new block pointing to the same next block;
        Block* unlinked_block;
        unlinked_block = new Block(this->GetCurrentBlock()->GetBlockNumber(), this->m_disk);
        //point this list to the next block;
        this->m_startBlockNumber = next_pointer;
        Block mBlock = Block(0, this->m_disk);
        mBlock.SetPointer(next_pointer, 0);
        mBlock.Write(this->m_disk);
        //return the unlinked block
        Rewind();

        return unlinked_block;
  }


  /**
   *Writes out the current start block of free list to block 0 and closes disk
   *return: true iff the Free List could be closed
   */
  bool Close(){
      delete this->m_seek;
      this->m_seek = new Block(0, this->m_disk);
      this->m_seek->SetPointer(this->m_startBlockNumber, 0);
      this->m_seek->Write(this->m_disk);
      m_disk->Close();

  }

  /**
   *Spawn new BlockGroup from the Free List
   *return: a new BlockGroup or NULL if one could not be created
   */
  BlockGroup* CreateNew(){

      if (this->m_numberOfBlocks>0){
        BlockGroup* newBlkGrp = new BlockGroup(-1, -1, 0, this);
        newBlkGrp->SetFreeList(this);
        
        Block* newBlk;// = new Block(Disk::DEFAULT_BLOCK_SIZE);
        newBlk= this->UnlinkBlock();
        Block newMaster(0, this->m_disk);
        newMaster.SetPointer(newBlk->GetPointer(0), 0);
        newMaster.Write(this->m_disk);
        newBlkGrp->Initialize(newBlk->GetBlockNumber());
        return newBlkGrp;
      }
      else return NULL;

  }

  /**
   *Returns all blocks of the BlockedLinkedList to the Free List
   *param: bll after this call, this BlockLinkedList has been deallocated
   */
  void Return(BlockLinkedList* bll){
      
      delete this->m_seek;
      bll->Rewind();
      this->m_seek = new Block(this->GetEndBlockNumber(),this->m_disk);
      this->GetCurrentBlock()->SetPointer(bll->GetCurrentBlock()->GetBlockNumber(),0);
      this->GetCurrentBlock()->Write(this->m_disk);
      int endBlock = bll->GetEndBlockNumber();
      if(endBlock != -1)
      {
          this->m_endBlockNumber = endBlock;
      }
      bll->~BlockLinkedList();
      bll = NULL;
  }

  static const int DEFAULT_NUMBER_OF_BLOCKS = 100;

};


#endif
